import { BaseComp } from './comp';
import { KeyFrameSequence } from '../animation/keyFrame';
import { hold } from '../animation';

let timerId = 0;

/**
 * @param {BaseComp} comp
 * @param {(c:BaseComp)=>any} getter
 */
function findFromParent(comp, getter) {
	while (comp = comp.parent) {
		let v = getter(comp)
		if (v) {
			return v;
		}
	}
	return null;
}

/**
 * 原先使用 zrender 的场景用到的 component
 */
class ZRenderComp extends BaseComp {
	/**
	 * @type {HTMLDivElement}
	 * 如果 div 为 null 就说明当前使用的是父节点传下来的 div, 否则就是当前 comp 创建的 div
	 */
	div = null;

	/** @type {Sprite[]} */ sprites = [];
	zlevels = {};

	/**
	 * @param {BaseComp} parent
	 */
	constructor(parent, zrender = null) {
		super(parent);
		this.zrender = zrender || findFromParent(this, (comp) => comp.zrender);

		this.ticker((now) => this._tickAllAnims(now));
	}

	/**
	 * @param {number} x 相对于父组件的 x 坐标
	 * @param {number} y 相对于父组件的 y 坐标
	 */
	useParentZRenderInstance(x = 0, y = 0, zr = null) {
		zr = zr ? zr : findFromParent(this, (comp) => comp.zr);
		if (!zr) {
			throw { msg : '无法从父组件中获取 zrender instance' };
		}

		this.zr = zr;
		this.x = x;
		this.y = y;

		this.defer(() => {
			this.sprites.forEach(s => {
				this.zr.remove(s.sp);
			});
		});
	}

	useExistZRenderInstance(zr, x, y) {
		this.useParentZRenderInstance(x, y, zr);
	}

	/**
	 * 用于创建一个新的 div 的 comp.
	 * 这种 comp 的 this.x 和 this.y 是当前 div 左上角和上一个组件锚点的相对坐标.
	 * @param {number} x 当前 div 相对上一个组件锚点的横坐标差
	 * @param {number} y 当前 div 相对上一个组件锚点的纵坐标差
	 * @param {number} divWidth
	 * @param {number} divHeight
	 * @param {string} elemName
	 * @param {number | undefined} divZLevel
	 * @param {string} parentElemId 创建的 div 在哪个 document 的 element 下
	 */
	createNewZRenderInstance(x, y, divWidth, divHeight, elemName, divZLevel = undefined, parentElemId = 'main') {
		this.basePosition = [0, 0];
		this.x = x;
		this.y = y;
		const [ax, ay] = this.absolutePos();

		const main = document.getElementById(parentElemId);
		const content = document.createElement('div');

		content.id = elemName;
		content.style.position = 'absolute';
		content.style.overflow = 'hidden';
		content.style.width = divWidth + 'px';
		content.style.height = divHeight + 'px';
		content.style.left = ax + 'px';
		content.style.top = ay + 'px';
		if (divZLevel != null) {
			content.style.zIndex = divZLevel + '';
		}
		main.appendChild(content);
		this.div = content;

		this.zr = this.zrender.init(content);
		this.defer(() => {
			this.zrender.dispose(this.zr);
			this.div.remove();
		});
	}

	/**
	 * @param {number} x
	 * @param {number} y
	 */
	move(x, y) {
		console.error('TODO');
		throw this;
	}

	/**
	 * @param {number} x
	 * @param {number} y
	 */
	moveTo(x, y) {
		this.x = x;
		this.y = y;
		if (this.div) {
			const apos = this.absolutePos();
			this.div.style.left = apos[0] + 'px';
			this.div.style.top = apos[1] + 'px';
		}

		const rpos = this.relativePos();
		this.sprites.forEach(s => {
			s._moveTo(rpos);
		});
	}

	/** *******
	 * Sprite *
	 ******** */

	/**
	 * @param {string} path
	 */
	createPathFromString(path) {
		let b = new SpriteBuilder(
			(args) => this._buildSprite(args,
				(o) => this.zrender.path.createFromString(o._path, o)), this.zrender);
		b.args._path = path;
		return b;
	}

	createCustomShape(Shape, shapeOption) {
		let b = new SpriteBuilder(
			(args) => this._buildSprite(args, (o) => new Shape(o)), this.zrender);
		b.args.shape = shapeOption;
		return b;
	}

	/**
	 * @param {number} x1
	 * @param {number} y1
	 * @param {number} x2
	 * @param {number} y2
	 */
	createLine(x1, y1, x2, y2) {
		let b = new SpriteBuilder(
			(args) => this._buildSprite(args, (o) => new this.zrender.Line(o)), this.zrender);
		b.args.shape = { x1, y1, x2, y2 };
		return b;
	}

	/**
	 * @param {number} width
	 * @param {number} height
	 * @param {number[]} r 圆角半径, 左上、右上、右下、左下角的半径
	 */
	createRect(width, height, x = 0, y = 0, r = [0, 0, 0, 0]) {
		let b = new SpriteBuilder(
			(args) => this._buildSprite(args, (o) => new this.zrender.Rect(o)), this.zrender);
		b.args.shape = { x, y, width, height, r };
		return b;
	}

	/**
	 * @param {number} r 半径
	 * @param {number} cx 圆心横坐标
	 * @param {number} cy 圆心纵坐标
	 */
	createCircle(r, cx = 0, cy = 0) {
		let b = new SpriteBuilder(
			(args) => this._buildSprite(args, (o) => new this.zrender.Circle(o)), this.zrender);
		b.args.shape = { r, cx, cy };
		return b;
	}

	/**
	 * 多边形
	 * @param  {...number[]} points 点坐标
	 */
	createPolygon(...points) {
		let b = new SpriteBuilder(
			(args) => this._buildSprite(args, (o) => new this.zrender.Polygon(o)), this.zrender);
		b.args.shape = { points };
		return b;
	}

	/**
	 * 多线段, 不闭合
	 * @param  {...number[]} points 点坐标
	 */
	createPolyline(...points) {
		let b = new SpriteBuilder(
			(args) => this._buildSprite(args, (o) => new this.zrender.Polyline(o)), this.zrender);
		b.args.shape = { points };
		return b;
	}

	/**
	 * @param {string | HTMLImageElement} src 访问路径, 比如 this.root.context.assetPath('assets/icon.png'),
	 * @param {number | undefined} width 图片最后显示成多宽
	 * @param {number | undefined} height 图片最后显示成多高
	 * @param {number | undefined} x 移多少位置, 不是裁剪图片
	 * @param {number | undefined} y 移多少位置, 不是裁剪图片
	 */
	createImage(src, width, height, x = 0, y = 0) {
		let b = new SpriteBuilder(
			(args) => this._buildSprite(args, (o) => new this.zrender.Image(o)), this.zrender);
		b.args.style = { image: src, x, y, width, height }; // 不是 shape
		return b;
	}

	createText(text = '', size = 28) {
		let b = new SpriteBuilder(
			(args) => this._buildSprite(args, (o) => new this.zrender.Text(o)), this.zrender);
		b.args.style = { text, fontSize: size }; // 不是 shape
		return b;
	}

	/**
	 * @param {object} args
	 * @param {Function} createFunc
	 */
	_buildSprite(args, createFunc) {
		if (args.position == null) {
			args.position = [0, 0];
		}
		const [offx, offy] = args.position;
		if (!args.clip) {
			const [x, y] = this.relativePos();
			args.position[0] += x;
			args.position[1] += y;

			if (args.origin) {
				args.position[0] -= args.origin[0];
				args.position[1] -= args.origin[1];
			}
		}

		const sp = createFunc(args);
		if (!args.clip) {
			const zlevel = args.zlevel == null ? 0 : args.zlevel;
			this.zlevels[zlevel] = true;
			this.zr.add(sp);
		}

		const warper = new Sprite(sp, this, offx, offy, args.clip);
		if (!args.clip) {
			this.sprites.push(warper);
		}
		return warper;
	}

	/** *********
	 *   动画   *
	 ********** */
	/*
	 * 这里启用的是关键帧动画.
	 * 不使用 ZRender 自己的帧动画是因为它的帧动画一旦你把画面隐藏 (比如浏览器最小化),
	 * 过一会儿恢复, 它的动画会错误.
	 */
	hasSpriteAnimation = false;

	/** @type {{seq:KeyFrameSequence,lastTime:number,name:string}[]} */ anims = [];

	getAnimX() {
		return this.createAnimation('x', this.x).whenUpdated(p => this.x = p.value);
	}

	getAnimY() {
		return this.createAnimation('y', this.y).whenUpdated(p => this.y = p.value);
	}

	timer() {
		const name = 'timer-' + (timerId++);
		let anim = this.createAnimation(name, -1);
		/** @type {{time:number,callback:(p:{done:()=>void})=>void}[]} */
		let list = [];

		let obj = {
			when : (/** @type {number} */ timeInMs, /** @type {(p:{done:()=>void})=>void} */ callback) => {
				list.push({ time : timeInMs, callback });
				return obj;
			},
			commit : () => {
				let u = anim.update();
				list.forEach((el, index) => u.when(el.time, index).easing(hold).callback(el.callback));
				u.commit();
			},
			getCleaner : () => { return () => this.removeAnimation(name); }
		};
		return obj;
	}

	/**
	 * 如果同名的 anim 存在, 会被替换掉
	 * @param {string} name
	 * @param {number|null} lastValue
	 */
	createAnimation(name, lastValue = null) {
		this.removeAnimation(name);

		let anim = new KeyFrameSequence(lastValue);
		this.anims.push({
			seq: anim, lastTime: Date.now(), name,
		});
		return anim;
	}

	getExistAnimation(name) {
		return this.anims.find(a => a.name == name)?.seq;
	}

	/**
	 * @param {number} now 
	 */
	_tickAllAnims(now) {
		const {anims} = this;
		if (anims.length == 0) {
			return;
		}

		// 将删掉已经执行完的 anim
		for (let i = anims.length - 1; i >= 0; i--) {
			let anim = anims[i];
			let seq = anim.seq;
			if (now <= anim.lastTime) {
				continue;
			}
			seq.tick(now - anim.lastTime);
			anim.lastTime = now;
			if (seq.done) {
				anims.splice(i, 1);
			}
		}
	}

	paint() {
		// 一般是 x, y 变动之后, 到这里的
		this.sprites.forEach(s => s.moveTo());
	}

	removeAnimation(name) {
		const { anims } = this;
		let index = anims.findIndex(a => a.name === name);
		if (index >= 0) {
			anims.splice(index, 1);
		}
	}

	/**
	 * 删除, 并强制执行完这个 animation
	 * @param {string} name 
	 */
	removeAndForceAnimation(name) {
		const { anims } = this;
		let index = anims.findIndex(a => a.name === name);
		if (index >= 0) {
			anims[index].seq.forceToEnd();
			anims.splice(index, 1);
		}
	}

	/**
	 * @param {(name:string) => boolean} filter 哪些 anim 被清除
	 */
	clearAnimations(filter = () => true) {
		this.anims = this.anims.filter(a => !filter(a.name));
	}
}

class Sprite {
	// 关键帧动画
	// /** @type {A[]} */ anims = [];
	// dirty = false;

	/**
	 * @param {ZRenderComp} comp
	 * @param {number} offx
	 * @param {number} offy
	 * @param {string} name
	 * @param {boolean} clip 是否声明为 clip path
	 */
	constructor(sp, comp, offx, offy, clip = false) {
		this.sp = sp;
		this.comp = comp;
		this.x = offx;
		this.y = offy;
		this.clip = clip;
	}

	moveTo(x = this.x, y = this.y) {
		this._moveTo(this.clip ? [] : this.comp.relativePos(), x, y);
		return this;
	}

	_moveTo(compRPos = this.clip ? [] : this.comp.relativePos(), x = this.x, y = this.y) {
		this.x = x;
		this.y = y;
		let pos = [x, y];
		if (!this.clip) {
			pos[0] += compRPos[0];
			pos[1] += compRPos[1];
			if (this.sp.origin) {
				pos[0] -= this.sp.origin[0];
				pos[1] -= this.sp.origin[1];
			}
		}
		this.sp.attr({ position: pos });
	}

	scaleTo(x, y = x) {
		this.sp.attr({ scale: [x, y] });
		this._moveTo();
		return this;
	}

	/**
	 * @returns {{x:number, y:number, width:number, height:number}}
	 */
	getBoundingRect() {
		return this.sp.getBoundingRect();
	}

	hide() { this.sp.hide(); return this; }
	show() { this.sp.show(); return this; }

	/**
	 * 设置锚点位置. x 和 y 是 scale = 1 时相对于图形的位置
	 * @param {number} x
	 * @param {number} y
	 */
	anchor(x, y) {
		this.origin = [x, y];
		this._moveTo();
		return this;
	}

	/** @param {number} rotation 带 PI 的那个, 不是角度 */
	rotation(rotation) { this.sp.attr({ rotation }); return this; }

	/**
	 * @param {Sprite} sprite
	 */
	setClipPath(sprite) {
		if (!sprite.clip) {
			throw { message : '你必须声明 sprite 为 clip 才能进行裁剪', sprite };
		}
		this.sp.setClipPath(sprite.sp);
	}

	attr(obj1, obj2) {
		this.sp.attr(obj1, obj2);
	}

	/** @param {number} o */
	opacity(o) { this.sp.attr({ style : { opacity : o } }); return this; }
	/** @param {number} o */
	lineDashOffset(o) { this.sp.attr({ style : { lineDashOffset : o } }); return this; }

	/**
	 * @param {boolean} forceToEnd 是否将动画跳到最后一帧
	 */
	stopAnimation(forceToEnd) {
		this.sp.stopAnimation(forceToEnd);
	}

	/**
	 * @param {string} type 类型, 可能是 'style' 'position' 'shape' 等
	 */
	animate(type) {
		return this.sp.animate(type);
	}

	rectShape() {
		let shape = {};
		let bundle = {
			/** @param {number} w */
			width : function(w) { shape.width = w; return bundle; },
			/** @param {number} h */
			height : function(h) { shape.height = h; return bundle; },
			/** @param {number} x */
			x : function(x) { shape.x = x; return bundle; },
			/** @param {number} y */
			y : function(y) { shape.y = y; return bundle; },
			/** @param {number[]} rs */
			r : function(rs) { shape.r = rs; return bundle; },
			update : () => {this.sp.attr({ shape }); shape = {}; return this;}
		}
		return bundle;
	}

	lineShape() {
		let shape = {};
		let bundle = {
			/** @param {number} x1 */
			x1 : function(x1) { shape.x1 = x1; return bundle; },
			/** @param {number} x2 */
			x2 : function(x2) { shape.x2 = x2; return bundle; },
			/** @param {number} y1 */
			y1 : function(y1) { shape.y1 = y1; return bundle; },
			/** @param {number} y2 */
			y2 : function(y2) { shape.y2 = y2; return bundle; },
			update : () => {this.sp.attr({ shape }); shape = {}; return this;}
		}
		return bundle;
	}

	polylineShape() {
		let shape = {};
		let bundle = {
			/** @param {number[][]} p */
			pointList : function(p) { shape.points = p; return bundle; },
			/** @param {number} t 圆滑程度，取值范围为 0 到 1 之间的数字，0 表示不圆滑. 用贝塞尔曲线插值算法 */
			smooth : function(t) { shape.smooth = t; return bundle; },
			/** 表示用 Catmull-Rom spline 插值算法 */
			asSpline : function() { shape.smooth = 'spline'; return bundle; },
			update : () => {this.sp.attr({ shape }); shape = {}; return this;}
		}
		return bundle;
	}

	style() {
		if (this.comp == null) {
			throw { sprite : this, err : '这个 sprite 还没加入到任何 component 上' };
		}
		if (this.comp.zrender == null) {
			throw { sprite : this, err : '这个 sprite 所在的 component 的 zrender 是空的' };
		}
		return new SpriteUpdater(this);
	}

	destroy() {
		this.comp.zr.remove(this.sp);
		const index = this.comp.sprites.findIndex(s => s == this);
		index >= 0 && this.comp.sprites.splice(index, 1);
		this.comp = null;
	}
}

class SpriteBaseUpdater {
	constructor(zrender) {
		this.zrender = zrender;
		this.args = {};
	}

	/**
	 * @param {string} color
	 */
	fill(color) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.fill = color;
		return this;
	}

	fillNone() {
		return this.fill('transparent');
	}

	/**
	 * @param { 'source-over' | 'source-in' | 'source-out' | 'source-atop' | 'destination-over'
	 * | 'destination-in' | 'destination-out' | 'destination-atop' | 'lighter' | 'copy' | 'xor'
	 * | 'multiply' | 'screen' | 'overlay' | 'darken' | 'lighten' | 'color-dodge' | 'color-burn'
	 * | 'hard-light' | 'soft-light' | 'difference' | 'exclusion' | 'hue' | 'saturation' | 'color'
	 * | 'luminosity' } op 叠加模式 globalCompositeOperation
	 */
	blend(op) {
		this.args.style || (this.args.style = {});
		this.args.style.blend = op;
		return this;
	}

	/**
	 * @param {number} x1
	 * @param {number} y1
	 * @param {number} x2
	 * @param {number} y2
	 * @param {array} colorFrameArray 比如下面的示例
	 * [
	 *   {offset:0, color:'#FFF'},
	 *   {offset:0.04, color:layout.colors.strokeBgL3},
	 *   {offset:0.86, color:layout.colors.strokeBgL1}
	 * ]
	 */
	fillLinearGradient(x1, y1, x2, y2, colorFrameArray) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.fill = new this.zrender.LinearGradient(x1, y1, x2, y2, colorFrameArray);
		return this;
	}

	/**
	 * @param {string} color 注意, 颜色只能用 string 不能渐变
	 * @param {number} lineWidth
	 */
	stroke(color, lineWidth) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.stroke = color;
		a.style.lineWidth = lineWidth;
		return this;
	}

	strokeNone() {
		return this.stroke('transparent', 0);
	}

	/**
	 * @param {number} lineWidth
	 * @param {number[]} xyArray [x1, y1, x2, y2]
	 * @param {array} colorFrameArray 比如下面的示例
	 * [
	 *   {offset:0, color:'#FFF'},
	 *   {offset:0.04, color:layout.colors.strokeBgL3},
	 *   {offset:0.86, color:layout.colors.strokeBgL1}
	 * ]
	 */
	strokeLinearGradient(lineWidth, xyArray, colorFrameArray) {
		const a = this.args;
		const [x1, y1, x2, y2] = xyArray;
		a.style || (a.style = {});
		a.style.stroke = new this.zrender.LinearGradient(x1, y1, x2, y2, colorFrameArray);
		a.style.lineWidth = lineWidth;
		return this;
	}

	/**
	 * @param {number[]} array
	 * @param {number} offset
	 */
	lineDash(array, offset = 0) {
		const a = this.args;
		a.style || (a.style = {});
		if (array) {
			a.style.lineDash = array;
		}
		a.style.lineDashOffset = offset;
		return this;
	}

	/**
	 * @param {string} color
	 * @param {number} blur
	 */
	shadow(color, blur, offsetX = 0, offsetY = 0) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.shadowColor = color;
		a.style.shadowBlur = blur;
		a.style.shadowOffsetX = offsetX;
		a.style.shadowOffsetY = offsetY;
		return this;
	}

	/**
	 * @param {string} color
	 * @param {number} blur
	 */
	textShadow(color, blur, offsetX = 0, offsetY = 0) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.textShadowColor = color;
		a.style.textShadowBlur = blur;
		a.style.textShadowOffsetX = offsetX;
		a.style.textShadowOffsetY = offsetY;
		return this;
	}

	/**
	 * @param {string} align 'center'
	 * @param {string} verticalAlign 'bottom'
	 * @param {string} weight 'bold'
	 * @param {string} fontFamily 'Microsoft YaHei UI'
	 */
	textStyle(align, verticalAlign, weight, fontFamily, fontStyle) {
		const a = this.args;
		a.style || (a.style = {});
		align && (a.style.textAlign = align);
		verticalAlign && (a.style.textVerticalAlign = verticalAlign);
		weight && (a.style.fontWeight = weight);
		fontFamily && (a.style.fontFamily = fontFamily);
		fontStyle && (a.style.fontStyle = fontStyle);
		return this;
	}

	/**
	 * @param {string} color 'black'
	 */
	textFill(color) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.textFill = color;
		return this;
	}

	/**
	 * @param {string} color 'black'
	 * @param {number} lineWidth
	 */
	textStroke(color, lineWidth) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.textStroke = color;
		a.style.textStrokeWidth = lineWidth;
		return this;
	}

	/**
	 * @param {number} x 
	 * @param {number} y 
	 * @param {number} width 
	 * @param {number} height 
	 */
	textRect(x, y, width, height) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.textRect = {x, y, width, height};
		return this;
	}

	/**
	 * @param {number} delta
	 */
	opacity(delta) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.opacity = delta;
		return this;
	}

	/**
	 * @param {number} zlevel
	 * @param {number} z
	 */
	z(zlevel, z) {
		const a = this.args;
		a.zlevel = zlevel;
		if (z != null) {
			a.z = z;
		}
		return this;
	}

	/**
	 * @param {number} x
	 * @param {number} y
	 */
	position(x, y) {
		this.args.position = [x, y];
		return this;
	}

	/**
	 * @param {number} x
	 * @param {number} y
	 */
	anchor(x, y) {
		this.args.origin = [x, y];
		return this;
	}

	/**
	 * @param {number} x
	 * @param {number} y
	 */
	scale(x, y = x) {
		this.args.scale = [x, y];
		return this;
	}

	/**
	 * @param {number} r
	 */
	rotation(r) {
		this.args.rotation = r;
		return this;
	}

	/**
	 * 声明它为 clipPath
	 */
	defineAsClip() {
		this.args.clip = true;
		return this;
	}
}

class SpriteBuilder extends SpriteBaseUpdater {
	/**
	 * @param {Function} factory
	 */
	constructor(factory, zrender) {
		super(zrender);
		this.factory = factory;
	}

	/**
	 * @returns {Sprite}
	 */
	build() {
		return this.factory(this.args);
	}
}

class SpriteUpdater extends SpriteBaseUpdater {
	/**
	 * @param {Sprite} sprite
	 */
	constructor(sprite) {
		super(sprite.comp.zrender);
		this.sprite = sprite;
	}

	/**
	 * @param {string} str
	 */
	text(str) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.text = str;
		return this;
	}

	/**
	 * @param {string | number} size
	 *   例如: 1.2em; x-small; smaller; 12px; 80%;
	 */
	fontSize(size) {
		const a = this.args;
		a.style || (a.style = {});
		a.style.fontSize = typeof(size) == 'number' ? size + 'px' : size;
		return this;
	}

	update() {
		this.sprite.sp.attr(this.args);
		if (this.args.position) {
			this.sprite.moveTo(this.args.position[0], this.args.position[1]);
		}
		this.args = {};
	}
}

export {
	ZRenderComp, Sprite
};
